package a2012c2.t6.chat.client;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collection;
import java.util.List;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;

import a2012c2.t6.chat.common.ConectionException;
import a2012c2.t6.chat.common.Consts;
import a2012c2.t6.chat.common.Mensaje;
import a2012c2.t6.chat.common.TextUtilities;

/**
 * Clase que simula ser un flat Client.
 */
public class Cliente extends JFrame {

	/** serializable. */
	private static final long serialVersionUID = 1L;

	/** El hilo que maneja la comunicacion con el servidor. */
	private ClientThread clientThread;

	/** campo de texto donde vamos a sacar la direccion del server. */
	private JTextField serverHost;

	/** campo de texto donde vamos a sacar el puerto del server. */
	private JTextField serverPort;

	/** campo de texto donde vamos a sacar nuestro Nick. */
	private JTextField userNick;

	/**
	 * campo de texto donde vamos a sacar el mensaje para mandar al server y
	 * escribir en el historial.
	 */
	private JTextField userMessage;

	/** Boton para tartar de conectarse al server. */
	private JButton btnConnect;

	/** Boton para tartar de desconectarse del server. */
	private JButton btnDisconnect;

	/** Boton para enviar al server. */
	private JButton btnSend;

	/** panel para escribir los mensajes que llegan del server. */
	private JTextPane messageHistory;

	/** lista de usuarios conectados. */
	private JList connectedUsers;

	/**
	 * Constructor inicializa la pantalla.
	 */
	public Cliente() {
		try {
			// Set System L&F
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		buildWindows();
	}

	/**
	 * construlle la pantalla.
	 */
	private void buildWindows() {
		setTitle("Grupo Z - 3�2�X - Chat");
		setBounds(0, 0, 720, 400);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.addWindowListener(new AddIconListener());
		getContentPane().setLayout(new BorderLayout());

		JPanel pnl;
		JLabel lbl;

		getContentPane().add(createTopPanel(), BorderLayout.NORTH);
		getContentPane().add(createConectedUserPanel(), BorderLayout.EAST);

		//
		// CENTER Panel - Panel con datos de mensajes
		//
		pnl = new JPanel();
		pnl.setLayout(new BorderLayout());
		pnl.setBackground(Color.GRAY);
		getContentPane().add(pnl, BorderLayout.CENTER);

		lbl = new JLabel("Historial de mensajes:");
		pnl.add(lbl, BorderLayout.NORTH);
		lbl.setForeground(Color.WHITE);
		messageHistory = new JTextPane();
		messageHistory.setBackground(Color.WHITE);
		messageHistory.setEditable(false);
		messageHistory.setContentType("text/html");
		pnl.add(messageHistory, BorderLayout.CENTER);

		pnl.add(createPanelSendMessage(), BorderLayout.SOUTH);
		enableInputs(true);
	}

	/**
	 * createTopPanel: Crea el panel norte, donde se encuentran los datos del
	 * servidor puerto y nuick.
	 * 
	 * @return {@link Component} panel norte
	 * @author dWajser
	 */
	private Component createTopPanel() {
		//
		// TOP Panel - Datos del Servidor
		//
		JPanel pnl = new JPanel();
		pnl.setLayout(null);
		pnl.setBackground(Color.LIGHT_GRAY);
		pnl.setPreferredSize(new Dimension(0, 34));

		JLabel lbl = new JLabel("Host:");
		pnl.add(lbl);
		lbl.setBounds(8, 8, 32, 22);
		serverHost = new JTextField("127.0.0.1");
		pnl.add(serverHost);
		serverHost.setBounds(40, 8, 100, 22);

		lbl = new JLabel("Port:");
		pnl.add(lbl);
		lbl.setBounds(160, 8, 32, 22);
		serverPort = new JTextField(String.valueOf(Consts.DEFAULT_SEVER_PORT));
		pnl.add(serverPort);
		serverPort.setBounds(192, 8, 100, 22);

		lbl = new JLabel("Nick:");
		pnl.add(lbl);
		lbl.setBounds(312, 8, 32, 22);
		userNick = new JTextField("mi nick");
		pnl.add(userNick);
		userNick.setBounds(344, 8, 100, 22);

		btnConnect = new JButton("Conectar");
		pnl.add(btnConnect);
		btnConnect.setBounds(450, 8, 120, 22);
		btnConnect.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				clientStart();
			}
		});
		btnDisconnect = new JButton("Desconectar");
		pnl.add(btnDisconnect);
		btnDisconnect.setBounds(578, 8, 120, 22);
		btnDisconnect.addActionListener(new CloseComunicationListener());
		return pnl;
	}

	/**
	 * createTopPanel: crea la lista de usuarios conectados.
	 * 
	 * @return {@link Component} panel Este
	 * @author dWajser
	 */
	private Component createConectedUserPanel() {
		//
		// JList - Nicks de los usuarios logueados
		//
		JPanel pnlAux = new JPanel();
		pnlAux.setLayout(new BorderLayout());
		pnlAux.setBackground(Color.RED);
		pnlAux.setPreferredSize(new Dimension(200, 0));

		JLabel lbl = new JLabel("Usuarios conectados:");
		pnlAux.add(lbl, BorderLayout.NORTH);
		connectedUsers = new JList();
		connectedUsers.setPreferredSize(new Dimension(200, 0));
		connectedUsers.setBackground(Color.ORANGE);
		// El m�todo setListData est� sobrecargado y acepta un Vector como
		// argumento.
		// entonces pueden usar directamente la lista de usuarios del mensaje.
		connectedUsers.setListData(new Object[] {});
		pnlAux.add(connectedUsers, BorderLayout.CENTER);
		return pnlAux;
	}

	/**
	 * createTopPanel: crea el panel para que el usuario escriba el mensaje.
	 * 
	 * @return {@link Component} panel SOUTH
	 * @author dWajser
	 */
	private Component createPanelSendMessage() {
		JPanel sendMessagePanel = new JPanel();
		sendMessagePanel.setLayout(new FlowLayout());
		sendMessagePanel.setBackground(Color.GRAY);
		JLabel lbl = new JLabel("Texto:");
		userMessage = new JTextField();
		userMessage.setPreferredSize(new Dimension(300, 30));
		userMessage.addKeyListener(new SendListener());
		btnSend = new JButton("Enviar");
		btnSend.addActionListener(new SendListener());
		sendMessagePanel.add(lbl);
		sendMessagePanel.add(userMessage);
		sendMessagePanel.add(btnSend);
		sendMessagePanel.add(createIconTable());
		return sendMessagePanel;
	}

	/**
	 * Construye la tabla de iconos
	 * 
	 * @return el componente que muestra los iconos posibles a ser enviados
	 * @author mateo.brizi
	 * */
	private Component createIconTable() {
		JMenuBar iconTable = new JMenuBar();
		IconFactory iconFactory = IconFactory.getInsatance();
		Collection<Icon> icons = iconFactory.getIcons();
		JMenu menu = new JMenu();
		JPanel aux = new JPanel(new GridLayout(icons.size() / 7, 7));
		for (Icon icon : icons) {
			IconButton button = new IconButton(icon);
			button.addActionListener(new AddIconListener());
			aux.add(button);
		}

		menu.setIcon(iconFactory.getDefaultIcon());
		menu.add(aux);
		iconTable.add(menu);
		menu.setPreferredSize(new Dimension((int) menu.getPreferredSize()
				.getWidth(), 25));
		return iconTable;
	}

	/**
	 * <h1>writeConnection:<br>
	 * Este metodo escribe en el panel que un usuario se conecto a la sala del
	 * chat
	 * 
	 * @param editor
	 *            donde se va a escribir
	 * @param user
	 *            que se conecto
	 * */
	private void writeConnection(JTextPane editor, String user) {
		String text = "<b>El usuario: " + user + " ha iniciado sesion<b>";
		// TextUtilities.writeText(text, editor);
	}

	/**
	 * Client start.
	 */
	private void clientStart() {
		if (clientThread == null) {
			enableInputs(false);
			clientThread = new ClientThread();
			clientThread.start();
			userNick.setEnabled(false);
		}
	}

	/**
	 * Enable inputs.
	 * 
	 * @param isEnable
	 *            the is enable
	 */
	private void enableInputs(boolean isEnable) {
		this.serverHost.setEnabled(isEnable);
		this.serverPort.setEnabled(isEnable);
		this.userNick.setEnabled(isEnable);
		this.btnConnect.setEnabled(isEnable);
		this.btnDisconnect.setEnabled(!isEnable);
	}

	/**
	 * Client stop.
	 */
	private void clientStop() {
		if (clientThread != null) {
			clientThread.terminate();
			clientThread = null;
			enableInputs(true);
		}
	}

	/**
	 * Client send.
	 */
	private void clientSend() {
		if (clientThread != null) {
			clientThread.sendText();
		} else {
			clientMessage("Usted no se ha conectado");
		}
	}

	/**
	 * Client error.
	 * 
	 * @param msg
	 *            the msg
	 */
	private void clientMessage(String msg) {
		JOptionPane.showMessageDialog(this, msg);
	}

	/**
	 * Client error.
	 * 
	 * @param msg
	 *            the msg
	 */
	private void clientError(Exception msg) {
		clientMessage(msg.getMessage());
		msg.printStackTrace();
	}

	/**
	 * The main method.
	 * 
	 * @param args
	 *            the arguments
	 */
	public static void main(String[] args) {
		Cliente cliente = new Cliente();
		cliente.setVisible(true);
	}

	/**
	 * Process menssage.
	 * 
	 * @param response
	 *            the response
	 * @throws BadLocationException
	 */
	private void processMenssage(Mensaje response) {
		if (response.getText() != null && !response.getText().isEmpty()) {
			TextUtilities.insertNewMessage(messageHistory, response);
		}
	}

	/**
	 * The Class ClientThread.
	 */
	private class ClientThread extends Thread {

		/** The skt. */
		private Socket socket;

		/** The oos. */
		private ObjectOutputStream oos;

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.lang.Thread#run()
		 */
		public void run() {
			try {
				dialogWithTheServer();
			} catch (ConectionException e) {
				enableInputs(true);
				clientError(e);
			}
		}

		
		/**
		 * Es el metodo que se encarga de dialogar con el servidor.
		 * 
		 * @author mateo.brizi
		 * 
		 */
		private void dialogWithTheServer() {
			ObjectInputStream ois = null;
			try {
				socket = new Socket(serverHost.getText(),
						Integer.parseInt(serverPort.getText()));
				oos = new ObjectOutputStream(socket.getOutputStream());
				sendLoguin(oos);
				// Quedarse en loop hasta que se corte
				ois = new ObjectInputStream(socket.getInputStream());
				while (socket.isConnected()) {
					Mensaje response = (Mensaje) ois.readObject();
					System.out.println(userNick.getText() + " recibe: "
							+ response);
					processResponse(response);
				}
			} catch (ConnectException e) {
				clientMessage("El servidor se encuentra caido");
			} catch (SocketException e) {
				clientMessage("usuario desconectado");
			} catch (ClassNotFoundException e) {
				throw new ConectionException(e);
			} catch (IOException e) {
				throw new ConectionException(e);
			} finally {
				closeConection(ois);
			}
		}

		
		/**
		 * Es el metodo que se encarga de cerrar la conexion con el server.
		 * 
		 * @author mateo.brizi
		 * 
		 * @param ois de la clase {@link ObjectInputStream}
		 */
		
		private void closeConection(ObjectInputStream ois) {
			clientStop();
			try {
				if (ois != null) {
					ois.close();
				}
			} catch (Exception e) {
				throw new ConectionException(e);
			}
			try {
				if (oos != null) {
					oos.close();
				}
			} catch (IOException e) {
				throw new ConectionException(e);
			}
			try {
				if (socket != null) {
					socket.close();
					socket = null;
				}
			} catch (IOException e) {
				throw new ConectionException(e);
			}
		}

		/**
		 * Send loguin.
		 * 
		 * @param oos
		 *            the oos
		 * @throws IOException
		 *             Signals that an I/O exception has occurred.
		 */
		private void sendLoguin(ObjectOutputStream oos) throws IOException {
			Mensaje request = new Mensaje();
			request.setNick(userNick.getText());
			request.setType(Mensaje.MessageType.LOGIN);
			oos.writeObject(request);
		}

		/**
		 * Terminate.
		 */
		public void terminate() {
			if (socket != null) {
				try {
					sendLogout();
					socket.close();
				} catch (IOException e) {
					clientError(e);
				}
			}
		}

		/**
		 * Send logout.
		 */
		private void sendLogout() {
			if (socket != null) {
				Mensaje request = new Mensaje();
				try {
					request.setType(Mensaje.MessageType.LOGOUT);
					request.setNick(userNick.getText().trim());
					request.getUsers().clear();
					oos.writeObject(request);
					connectedUsers.setListData(new Object[0]);
				} catch (IOException e) {
					clientError(e);
				}
			}
		}

		/**
		 * Send text.
		 * 
		 * @throws BadLocationException
		 */
		@SuppressWarnings("unchecked")
		public void sendText() {
			if (socket != null) {
				Mensaje request = new Mensaje();
				try {
					request.setType(Mensaje.MessageType.MESSAGE);
					request.setNick(userNick.getText().trim());
					request.setText(userMessage.getText().trim());
					if (Cliente.this.connectedUsers.getSelectedValue() != null) {
						request.setUsers(new Vector<String>(
								(List<String>) Cliente.this.connectedUsers
										.getSelectedValuesList()));
					}
					// mando al server
					oos.writeObject(request);
					// limpio lo que escribe en la pantalla
					userMessage.setText("");
					TextUtilities.insertNewMessage(messageHistory, request);
				} catch (Exception e) {
					clientError(e);
				}

			}
		}

		/**
		 * processResponse: procesa lo que venga del server dispatcher de
		 * proceso segun el tipo de mensaje.
		 * 
		 * @param response
		 *            mensaje que viene del servidor
		 * @throws IOException
		 *             Signals that an I/O exception has occurred.
		 * @author dWajser
		 */
		private void processResponse(Mensaje response) throws IOException {
			System.out.println("server response: " + response);
			switch (response.getType()) {
			case LIST:
				processList(response);
				break;
			case MESSAGE:
				processMenssage(response);
				break;
			case LOGOUT:
				processLogOut(response);
				break;
			case ERROR:
				processError(response);
				break;
			default:
				break;
			}
		}

		private void processError(Mensaje response) {
			TextUtilities.writeText(response.getText(), messageHistory);
			clientStop();
		}


		private void processLogOut(Mensaje response) {
			System.out.println("processLogOut :: response: " + response);
		}


		/**
		 * Process list.
		 * 
		 * @param response
		 *            the response
		 */
		private void processList(Mensaje response) {
			if (Cliente.this.connectedUsers.getModel().getSize() > 0) {
				List<String> connectedUsers = response.getUsers();
				for (String userFromServer : connectedUsers) {
					int i = 0;
					// si el primero soy yo no muestro nada
					boolean isInList = userFromServer.equals(userNick.getText()
							.trim());
					while (i < Cliente.this.connectedUsers.getModel().getSize()
							&& !isInList) {
						// recorro los usuarios de la lista
						String userInClient = (String) Cliente.this.connectedUsers
								.getModel().getElementAt(i);
						isInList = userInClient.equals(userFromServer)
								|| userFromServer.equals(userNick.getText()
										.trim());
						i++;
					}
					// si no esta en la lista escribo que entro uno nuevo en la
					// sala
					if (!isInList) {
						writeConnection(messageHistory, userFromServer);
					}
				}
			}
			Cliente.this.connectedUsers.setListData(response.getUsers());
			Cliente.this.connectedUsers.repaint();
		}
	}

	/**
	 * The listener interface for receiving send events. The class that is
	 * interested in processing a send event implements this interface, and the
	 * object created with that class is registered with a component using the
	 * component's <code>addSendListener<code> method. When
	 * the send event occurs, that object's appropriate
	 * method is invoked.
	 * 
	 * @see SendEvent
	 */
	public class SendListener extends KeyAdapter implements ActionListener {

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(ActionEvent paramActionEvent) {
			clientSend();
		}

		@Override
		public void keyPressed(KeyEvent e) {
			super.keyPressed(e);
			if (e.getKeyCode() == KeyEvent.VK_ENTER) {
				clientSend();
			}
		}
	}

	/**
	 * Se encarga de cerrar las conecciones
	 * 
	 * @author dwajser
	 * 
	 */
	public class CloseComunicationListener extends WindowAdapter implements
			ActionListener {

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(ActionEvent paramActionEvent) {
			clientStop();
		}
	}

	/**
	 * Se encarga de poner los iconos en donde escribe el usuario
	 * 
	 * @author dwajser
	 * 
	 */
	public class AddIconListener extends WindowAdapter implements
			ActionListener {

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(ActionEvent paramActionEvent) {
			if (paramActionEvent.getSource() instanceof IconButton) {
				IconButton button = (IconButton) paramActionEvent.getSource();
				userMessage.setText(userMessage.getText() + " "
						+ button.getRepresntativeIcon().getKey() + " ");
				messageHistory.transferFocus();
				messageHistory.transferFocusBackward();
				messageHistory.transferFocusUpCycle();
			}

		}
	}
}
